package com.dimao.certificate_build_service.pdf.kotiln

import com.dimao.certificate_build_service.Util.Log.Log
import com.dimao.certificate_build_service.pdf.PDFConfig
import com.dimao.certificate_build_service.pdf.ValueFactory
import io.vertx.core.Future
import io.vertx.core.Vertx
import io.vertx.core.json.JsonObject
import io.vertx.core.shareddata.AsyncMap
import io.vertx.kotlin.coroutines.awaitResult
import io.vertx.kotlin.coroutines.dispatcher
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.lang.Exception
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue

class PDFBuildQueue {
    companion object {
        private lateinit var queue: Queue<JsonObject>
        private lateinit var verxt: Vertx
        lateinit var consumerName: String
        @JvmStatic
        fun init(name: String, v: Vertx, c: PDFConfig): Future<Void> {
            PDFBuilder.init(v, c)
            queue = ConcurrentLinkedQueue<JsonObject>()
            verxt = v
            consumerName = name

            v.eventBus().localConsumer<JsonObject>(name){ message ->
                queue.add(message.body())
                getAsyncMap().compose { map ->
                    return@compose Future.future<Boolean> { map.get("running", it) }
                }.compose { running ->
                    if(!running) return@compose start()
                    else return@compose Future.succeededFuture<Boolean>()
                }.setHandler { result ->
                    if(result.failed())
                        Log.error("Get Or Change Running Result Failed", result.cause())
                }
            }

            return getAsyncMap().compose { map ->
                return@compose Future.future<Void> { map.put("running", false, it) }
            }
        }

        private fun start(): Future<Boolean> {
            build().recover { error ->
                        if(error !is NoSuchElementException)
                            return@recover Future.failedFuture(error)
                        else return@recover sleep()
                    }
                    .setHandler {
                        if(it.failed()) Log.error("PDFBuildQueue Build Failed", it.cause())
                    }
            return getAsyncMap().compose { map ->
                Future.future<Boolean> { promise ->
                    map.replace("running", true, promise)
                }
            }
        }

        private fun build(): Future<Boolean> {
            return Future.future<Boolean> { promise ->
                try {
                    val item = queue.remove()
                    val taskID = item.getString("task_id")
                    val buildInfo = item.getJsonObject("data")
                    val certificateID = buildInfo.getString("certificate_id")
                    val pdfType = buildInfo.getString("pdf_type")
                    val students = buildInfo.getJsonArray("students")
                    val pdfBuilder = when {
                        (pdfType == "type_one")
                                && (students.size() <= 6) -> PDFBuilder.getBuilder(ValueFactory.produceTypeOne(), "type_one_6_students")
                        (pdfType == "type_two")
                                && (students.size() <= 5) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_5_students")
                        (pdfType == "type_one")
                                && (students.size() > 6)
                                && (students.size() <= 8) -> PDFBuilder.getBuilder(ValueFactory.produceTypeOne(), "type_one_8_students")
                        (pdfType == "type_two")
                                && (students.size() == 6) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_6_students")
                        (pdfType == "type_two")
                                && (students.size() > 6)
                                && (students.size() <= 8) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_8_students")
                        (pdfType == "type_one")
                                && (students.size() > 8)
                                && (students.size() <= 10) -> PDFBuilder.getBuilder(ValueFactory.produceTypeOne(), "type_one_10_students")
                        (pdfType == "type_two")
                                && (students.size() > 8)
                                && (students.size() <= 10) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_10_students")
                        else -> throw Exception("Pdf Type Illegality")
                    }
                    val pdfBuilderTemporary = when {
                        (pdfType == "type_one")
                                && (students.size() <= 6) -> PDFBuilder.getBuilder(ValueFactory.produceTypeOne(), "type_one_6_students_temporary")
                        (pdfType == "type_two")
                                && (students.size() <= 5) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_5_students_temporary")
                        (pdfType == "type_one")
                                && (students.size() > 6)
                                && (students.size() <= 8) -> PDFBuilder.getBuilder(ValueFactory.produceTypeOne(), "type_one_8_students_temporary")
                        (pdfType == "type_two")
                                && (students.size() == 6) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_6_students_temporary")
                        (pdfType == "type_two")
                                && (students.size() > 6)
                                && (students.size() <= 8) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_8_students_temporary")
                        (pdfType == "type_one")
                                && (students.size() > 8)
                                && (students.size() <= 10) -> PDFBuilder.getBuilder(ValueFactory.produceTypeOne(), "type_one_10_students_temporary")
                        (pdfType == "type_two")
                                && (students.size() > 8)
                                && (students.size() <= 10) -> PDFBuilder.getBuilder(ValueFactory.produceTypeTwo(), "type_two_10_students_temporary")
                        else -> throw Exception("Pdf Type Illegality")
                    }
                    GlobalScope.launch (verxt.dispatcher()) {
                        val dirFuture = async {
                            val exists = awaitResult<Boolean> { verxt.fileSystem().exists("${PDFBuilder.config.savePDFPath}/$taskID", it) }
                            if(!exists) awaitResult<Void> { verxt.fileSystem().mkdir("${PDFBuilder.config.savePDFPath}/$taskID", it) }
                        }
                        val temporaryDirFuture = async {
                            val exists = awaitResult<Boolean> { verxt.fileSystem().exists("${PDFBuilder.config.saveTemporaryPDFPath}/$taskID", it) }
                            if(!exists) awaitResult<Void> { verxt.fileSystem().mkdir("${PDFBuilder.config.saveTemporaryPDFPath}/$taskID", it) }
                        }

                        dirFuture.await()
                        temporaryDirFuture.await()

                        launch {
                            val buffer = pdfBuilder.build(buildInfo)
                            awaitResult<Void> { verxt.fileSystem().writeFile("${PDFBuilder.config.savePDFPath}/$taskID/$certificateID.pdf", buffer, it) }
                        }

                        launch {
                            val buffer = pdfBuilderTemporary.build(buildInfo)
                            awaitResult<Void> { verxt.fileSystem().writeFile("${PDFBuilder.config.saveTemporaryPDFPath}/$taskID/$certificateID.pdf", buffer, it) }
                        }
                    }.invokeOnCompletion {
                        if(it != null) promise.fail(it)
                        else promise.complete(true)
                    }
                }catch (e: Exception){
                    promise.fail(e)
                }
            }.compose {
                build()
            }
        }

        private fun sleep(): Future<Boolean> {
            return getAsyncMap().compose { map ->
                Future.future<Boolean> { promise ->
                    map.replace("running", false, promise)
                }
            }
        }

        private fun getAsyncMap(): Future<AsyncMap<String, Boolean>> {
            return Future.future{promise ->
                verxt.sharedData().getLocalAsyncMap<String, Boolean>("BuildService/pdfBuildQueue/RunningMap"){
                    if(it.failed()) {
                        Log.error(" Get BuildService/pdfBuildQueue/RunningMap Error", it.cause())
                        promise.fail(it.cause())
                    }else promise.complete(it.result())
                }
            }
        }
    }
}